﻿using System;
using System.Linq;
using MadLibrary.Net.Pop3.Entities;
using MadLibrary.Net.Pop3.Exceptions;

namespace MadLibrary.Net.Pop3
{
    /// <summary>
    /// Pop3 Client
    /// </summary>
    public class Pop3Client : IDisposable
    {
        private const string Pop3ServerResponseOk = "+OK";
        private const string EndOfMessage = "\r\n.\r\n";

        /// <summary>
        /// Pop3 Server IP
        /// </summary>
        public string Pop3ServerIp { get; private set; }

        /// <summary>
        /// Pop3 Username
        /// </summary>
        public string Pop3Username { get; private set; }

        /// <summary>
        /// Pop3 Password
        /// </summary>
        public string Pop3Password { get; private set; }

        private readonly Pop3TcpClient _pop3TcpClient;

        /// <summary>
        /// POP3 client.
        /// </summary>
        /// <param name="serverIp">POP3 server IP or host to connect to.</param>
        /// <param name="username">Account username.</param>
        /// <param name="password">Account password.</param>
        /// <param name="useSsl">Use SSL? (port 995).</param>
        public Pop3Client(string serverIp, string username, string password, bool useSsl = false)
        {
            Pop3ServerIp = serverIp;
            Pop3Username = username;
            Pop3Password = password;

            _pop3TcpClient = new Pop3TcpClient(useSsl)
            {
                SendTimeout = 5000,
                ReceiveTimeout = 15000,
                ConnectTimeout = 15000
            };
        }

        /// <summary>
        /// Login to the POP3 server with the configured user/pass.
        /// </summary>
        public void Login()
        {
            _pop3TcpClient.Connect(Pop3ServerIp);

            var readBuffer = _pop3TcpClient.ReadTextUntilEnd(Environment.NewLine);

            if(!readBuffer.StartsWith(Pop3ServerResponseOk))
                throw new Pop3FailedLoginException(readBuffer);

            //  Set user name
            _pop3TcpClient.WriteTextLine("USER " + Pop3Username);

            readBuffer = _pop3TcpClient.ReadTextUntilEnd(Environment.NewLine);

            if(!readBuffer.StartsWith(Pop3ServerResponseOk))
                throw new Pop3FailedLoginException(readBuffer);

            //  Set password
            _pop3TcpClient.WriteTextLine("PASS " + Pop3Password);

            readBuffer = _pop3TcpClient.ReadTextUntilEnd(Environment.NewLine);

            if(!readBuffer.StartsWith(Pop3ServerResponseOk))
                throw new Pop3FailedLoginException(readBuffer);
        }

        /// <summary>
        /// Log off the current POP3 server
        /// </summary>
        public void Logoff()
        {
            VerifyConnectionToPop3Server();

            _pop3TcpClient.WriteTextLine("QUIT");

            var readBuffer = _pop3TcpClient.ReadTextUntilEnd(Environment.NewLine);

            if(!readBuffer.StartsWith(Pop3ServerResponseOk))
                throw new Pop3Exception(readBuffer);

            _pop3TcpClient.Close();
        }

        /// <summary>
        /// Shows the current messages that are waiting for retrival.
        /// </summary>
        /// <returns></returns>
        public Pop3Stats ShowStats()
        {
            VerifyConnectionToPop3Server();

            _pop3TcpClient.WriteTextLine("STAT");

            var readBuffer = _pop3TcpClient.ReadTextUntilEnd(Environment.NewLine);

            if(!readBuffer.StartsWith(Pop3ServerResponseOk))
                throw new Pop3Exception(readBuffer);

            var statParts = readBuffer.Split(' ');

            return new Pop3Stats
            {
                MessagesCount = int.Parse(statParts[1]),
                MessagesSize = long.Parse(statParts[2])
            };
        }

        /// <summary>
        /// Gets the message details for every message in queue.
        /// </summary>
        /// <returns></returns>
        public Pop3MessageInfo[] ListMessages()
        {
            VerifyConnectionToPop3Server();

            _pop3TcpClient.WriteTextLine("LIST");

            var readBuffer = _pop3TcpClient.ReadTextUntilEnd(EndOfMessage);

            if(!readBuffer.StartsWith(Pop3ServerResponseOk))
                throw new Pop3Exception(readBuffer);

            var listParts = readBuffer.Split('\n');

            var listSummaryParts = listParts[0].Split(' ');
            var numberOfMessages = int.Parse(listSummaryParts[1]);

            var messageInfos = new Pop3MessageInfo[numberOfMessages];

            for(var i = 0; i < numberOfMessages; i++)
            {
                var listPart = listParts[i + 1];
                var messageInfoParts = listPart.Trim('\r').Split(' ');

                messageInfos[i] = new Pop3MessageInfo
                {
                    Number = int.Parse(messageInfoParts[0]),
                    Size = long.Parse(messageInfoParts[1]),
                };
            }

            return messageInfos;
        }

        /// <summary>
        /// Download all the queued messages from the server.
        /// </summary>
        /// <returns></returns>
        public Pop3Message[] DownloadMessages()
        {
            VerifyConnectionToPop3Server();

            var messageInfos = ListMessages();

            return messageInfos
                .Select(m => DownloadMessage(m.Number))
                .ToArray();
        }

        /// <summary>
        /// Download a specific messaged from the server
        /// </summary>
        /// <param name="messageNumber">Specific message messageNumber to download.</param>
        /// <returns></returns>
        public Pop3Message DownloadMessage(int messageNumber)
        {
            VerifyConnectionToPop3Server();

            _pop3TcpClient.WriteTextLine("RETR " + messageNumber);

            var readBuffer = _pop3TcpClient.ReadTextUntilEnd(EndOfMessage);

            if(!readBuffer.StartsWith(Pop3ServerResponseOk))
                throw new Pop3Exception(readBuffer);

            var headerEndPosition = readBuffer.IndexOf("\r\n", StringComparison.Ordinal);

            var messageData =
                readBuffer.Substring(
                    headerEndPosition + 2,
                    readBuffer.Length - headerEndPosition - 2 - EndOfMessage.Length);

            return new Pop3Message
            {
                Number = messageNumber,
                Data = messageData
            };
        }

        /// <summary>
        /// Remove a specific messaged from the server.
        /// </summary>
        /// <param name="messageNumber">Specific message messageNumber to remove.</param>
        public void RemoveMessage(int messageNumber)
        {
            VerifyConnectionToPop3Server();

            _pop3TcpClient.WriteTextLine("DELE " + messageNumber);

            var readBuffer = _pop3TcpClient.ReadTextUntilEnd(Environment.NewLine);

            if(!readBuffer.StartsWith(Pop3ServerResponseOk))
                throw new Pop3Exception(readBuffer);
        }

        /// <summary>
        /// Pings the server to ensure the connection is alive.
        /// </summary>
        public void Ping()
        {
            VerifyConnectionToPop3Server();

            _pop3TcpClient.WriteTextLine("NOOP");

            var readBuffer = _pop3TcpClient.ReadTextUntilEnd(Environment.NewLine);

            if(!readBuffer.StartsWith(Pop3ServerResponseOk))
                throw new Pop3FailedLoginException(readBuffer);
        }

        /// <summary>
        /// Undo all remove message operation in the current connection.
        /// </summary>
        public void UndoDeletedMessages()
        {
            VerifyConnectionToPop3Server();

            _pop3TcpClient.WriteTextLine("RSET");

            var readBuffer = _pop3TcpClient.ReadTextUntilEnd(Environment.NewLine);

            if(!readBuffer.StartsWith(Pop3ServerResponseOk))
                throw new Pop3FailedLoginException(readBuffer);
        }

        /// <summary>
        /// Dispose
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
        }

        /// <summary>
        /// Dispose
        /// </summary>
        /// <param name="managedalso"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool managedalso)
        {
            if(managedalso)
            {
                if(_pop3TcpClient.Connected)
                    Logoff();
            }

            GC.SuppressFinalize(this);
        }

        private void VerifyConnectionToPop3Server()
        {
            if(_pop3TcpClient.Connected)
                throw new Pop3Exception("POP3 server connection is closed.");
        }
    }
}